package org.linuxsogood.boot.chat;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * @author honway.liu
 * @date 2019/12/1 9:08 下午
 * @email gm100861@gmail.com
 * @blog http://linuxsogood.org
 */
@Slf4j
public class NioChatServer {

    private Selector selector;
    private ServerSocketChannel serverSocketChannel;

    private static final String BIND = "0.0.0.0";
    private static final int PORT = 6666;

    public NioChatServer() throws IOException {

        // 1.获得serverSocketChannel
        serverSocketChannel = ServerSocketChannel.open();

        // 2.监听端口
        serverSocketChannel.socket().bind(new InetSocketAddress(BIND, PORT));

        // 3.设置服务端channel非阻塞
        serverSocketChannel.configureBlocking(false);

        // 4.获得selector
        selector = Selector.open();

        // 5. serverSocketChannel 注册到selector
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);


    }

    public void listen() throws IOException {
        // 阻塞2秒等侍, 看是否有就绪的事件, 如果有就绪的事件, 返回的值大于0
        // 返回的是就绪的通道数量
        while (true) {
            int readyCount = selector.select(2000);
            if (readyCount > 0) {
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();

                    if (!key.isValid()) {
                        log.error("selection key is invalid: {}", key.toString());
                    }

                    if (key.isAcceptable()) {
                        SocketChannel channel = serverSocketChannel.accept();
                        log.info("a connection was accepted by a ServerSocketChannel: {}", channel.getRemoteAddress());

                        channel.configureBlocking(false);
                        channel.register(selector, SelectionKey.OP_READ);
                    } else if (key.isConnectable()) {
                        SocketChannel channel = (SocketChannel) key.channel();
                        log.info("a connection was established with a remote server: {}", channel.getRemoteAddress());

                    } else if (key.isReadable()) {
                        SocketChannel channel = (SocketChannel) key.channel();
                        log.info("a channel is ready for reading: {}", channel.getRemoteAddress());

                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        int read = channel.read(buffer);
                        if (read > 0) {
                            String msg = new String(buffer.array());
                            log.info("read data from: [{}] msg: [{}]", channel.getRemoteAddress(), msg);
                            broadcastMsg(msg, channel);
                        }

                    } else if (key.isWritable()) {
                        SocketChannel channel = (SocketChannel) key.channel();
                        log.info("a channel is ready for writing: {}", channel.getRemoteAddress());
                    }
                    // 移除key, 避免重复处理
                    iterator.remove();
                }
            }
        }
    }

    /**
     * 广播消息, 除了自身
     * @param msg 消息
     * @param channel 当前channel
     */
    private void broadcastMsg(String msg, SocketChannel channel) throws IOException {
        Set<SelectionKey> selectionKeys = selector.keys();
        for (SelectionKey key : selectionKeys) {
            if (key.channel() instanceof SocketChannel && key.channel() != channel) {
                SocketChannel socketChannel = (SocketChannel) key.channel();
                socketChannel.write(ByteBuffer.wrap(msg.getBytes()));
            }
        }
    }

    public static void main(String[] args) throws IOException {
        NioChatServer nioChatServer = new NioChatServer();
        nioChatServer.listen();

    }
}
